import * as step from '@flow-step/step-toolkit'
import * as commandExists from 'command-exists'
import * as path from 'path'
import * as fs from 'fs'
import process from 'process'

const ShellBash = 'bash'
const ShellSh = 'sh'
const ShellPwsh = 'pwsh'
const ShellPowershell = 'powershell'
const ShellCmd = 'cmd'
const UTF8BOMPrefix = '\ufeff'

export async function runUserScript(
    shellName: string,
    shellScriptFile: string
): Promise<number> {
    shellScriptFile = renameScriptFile(shellName, shellScriptFile)
    wrapScriptFile(shellName, shellScriptFile)

    const skipLogCommandContent = process.env['FLOW_RUNNER_SKIP_LOG_COMMAND_CONTENT']
    if (skipLogCommandContent === undefined || skipLogCommandContent === '') {
        // 输出用户指令内容
        step.infoCyan(`------ User Command Content ------`)
        console.log(fs.readFileSync(shellScriptFile, 'utf8'))
        step.infoCyan(`------ User Command Content ------`)
    }

    let args = generateShellArgs(shellName, shellScriptFile)
    setEnvVarsFromArtifactEnv()
    return step.exec.call(shellName, args, {
        listener: {
            stdoutLine: (line: string) => {
                step.info(line)
            },
            stderrLine: (line: string) => {
                step.info(line)
            }
        }
    })
}

function wrapScriptFile(shellName: string, scriptFile: string) {
    let content = fs.readFileSync(scriptFile, 'utf8')
    let wrappedContent = wrapScriptContent(shellName, content)
    fs.writeFileSync(scriptFile, wrappedContent, 'utf8')
}

function wrapScriptContent(shellName: string, scriptContent: string) {
    let platform = step.platform.getPlatform()
    let newLine = platform == step.platform.PLATFORM_WIN ? '\r\n' : '\n'
    switch (shellName) {
        case ShellCmd:
            scriptContent = scriptContent.replace(/\n/g, '\r\n')
            return (
                'chcp 65001' + newLine + '@echo off' + newLine + scriptContent
            )
        case ShellPwsh:
        case ShellPowershell:
            scriptContent = scriptContent.replace(/\n/g, '\r\n')
            return (
                UTF8BOMPrefix +
                ("$ErrorActionPreference = 'stop'" +
                    newLine +
                    '[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8' +
                    newLine +
                    scriptContent +
                    newLine +
                    'if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }')
            )
        default:
            return scriptContent
    }
}

function renameScriptFile(
    shellName: string,
    originShellScriptFile: string
): string {
    let extension
    switch (shellName) {
        case ShellBash:
        case ShellSh:
            extension = '.sh'
            break
        case ShellPwsh:
        case ShellPowershell:
            extension = '.ps1'
            break
        case ShellCmd:
            extension = '.cmd'
            break
        default:
            extension = '.script'
    }
    let newShellScriptFilePath = path.format({
        ...path.parse(originShellScriptFile),
        base: '',
        ext: extension
    })
    fs.renameSync(originShellScriptFile, newShellScriptFilePath)
    return newShellScriptFilePath
}

export function getDefaultShell(): string {
    let availableShells: string[]
    let platform = step.platform.getPlatform()
    if (platform == step.platform.PLATFORM_WIN) {
        availableShells = ['pwsh', 'powershell', 'cmd']
    } else {
        availableShells = ['bash', 'sh']
    }

    for (let i = 0; i < availableShells.length; i++) {
        let shell = availableShells[i]
        let shellExists = commandExists.sync(shell)
        if (shellExists) {
            return shell
        }
    }

    return 'bash'
}

function generateShellArgs(
    shellName: string,
    shellScriptFile: string
): string[] {
    switch (shellName) {
        case ShellBash:
            return [
                '--noprofile',
                '--norc',
                '-e',
                '-o',
                'pipefail',
                shellScriptFile
            ]
        case ShellSh:
            return ['-e', shellScriptFile]
        case ShellPwsh:
            return ['-Command', `. \'${shellScriptFile}\'`]
        case ShellPowershell:
            return ['-Command', `. \'${shellScriptFile}\'`]
        case ShellCmd:
            return [
                '/D',
                '/E:ON',
                '/V:OFF',
                '/S',
                '/C',
                `CALL ${shellScriptFile}`
            ]
    }
    return [shellScriptFile]
}

function setEnvVarsFromArtifactEnv(): void {
    const artifactsValue = process.env['ARTIFACTS']
    let envVariables = extractEnvVarsFromArtifactContent(artifactsValue)
    for (let [envKey, envValue] of envVariables) {
        process.env[envKey] = envValue
    }
}

export function extractEnvVarsFromArtifactContent(
    artifactsValue: string | undefined
): Map<string, string> {
    if (artifactsValue == undefined) {
        return new Map<string, string>()
    }

    let jsonValue = artifactsValue
    if (isBase64String(artifactsValue)) {
        jsonValue = decodeBas64String(artifactsValue)
    }
    return extractVariableFromJson(jsonValue)
}

function extractVariableFromJson(jsonDataStr: string): Map<string, string> {
    let result = new Map<string, string>()

    // 兼容 "" 的场景，否则 JSON.parse 会报错
    if (jsonDataStr.length == 0) {
        return result
    }

    let jsonData = JSON.parse(jsonDataStr)
    for (let key in jsonData) {
        let valueStr = jsonData[key] as string
        if (isJsonObject(valueStr) ) {
            let valueObj = JSON.parse(valueStr)
            let dockerUrl = valueObj['DOCKER_URL'] as string
            if (dockerUrl != undefined && dockerUrl.length > 0) {
                result.set(key, dockerUrl)
                continue
            }

            let artifactUrl = valueObj['ARTIFACT_URL'] as string
            if (artifactUrl != undefined && artifactUrl.length > 0) {
                result.set(key, artifactUrl)
            }
        } else if (valueStr.includes('${')) {
            const envVarPattern = /\$\{(\w+)\}/g
            valueStr = valueStr.replace(envVarPattern, (match, key) => {
                return process.env[key] || match
            });
            result.set(key, valueStr);
        } else {
            result.set(key, valueStr)
        }
    }
    return result
}
function isJsonObject(str: string): boolean {
    try {
        const obj = JSON.parse(str);
        return obj && typeof obj === 'object'
    } catch (e) {
        return false;
    }
}



function decodeBas64String(inputContent: string): string {
    let buff = Buffer.from(inputContent, 'base64')
    return buff.toString()
}

function isBase64String(inputContent: string): boolean {
    const base64regex =
        /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/
    return base64regex.test(inputContent)
}
